/*
 * Copyright 2009 Brook Smith brook.smith@paradise.net.nz.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package org.javagenerate.generator;


import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

public abstract class BaseGenerator {

	protected List<AccessorDefinition> beanMethods = new ArrayList<AccessorDefinition>();

	private List<String> implementsDecl = new ArrayList<String>();

	private Set<String> importsDecl = new HashSet<String>();

	private List<VariableDefinition> localVariables = new ArrayList<VariableDefinition>();

	private List<ConstructorDefinition> constructors = new ArrayList<ConstructorDefinition>();

	private List<MethodDefinition> methods = new ArrayList<MethodDefinition>();

	private boolean runPrewrite = false;

	private boolean isAbstract = false;

	private String visability = "public";

	private boolean isClass = true;

	private String annotations = null;
	
	private String code = null;
	
	private String typeStatement = null;

	public String getTypeStatement() {
		return typeStatement;
	}

	public void setTypeStatement(String typeStatement) {
		this.typeStatement = typeStatement;
	}

	/**
	 * Code is not resolved and is just injected straight into the main code block
	 * of the respective class
	 * @return
	 */
	
	public String getCode() {
		return code;
	}

	public void setCode(String code) {
		this.code = code;
	}

	public void addBeanMethods(AccessorDefinition beanDefinition) {
		beanMethods.add(beanDefinition);
	}

	public void addConstructor(ConstructorDefinition constructorDefinition) {
		constructors.add(constructorDefinition);
	}

	public void addLocalVariable(VariableDefinition definition) {
		localVariables.add(definition);
	}

	public void addMethod(MethodDefinition definition) {
		methods.add(definition);
	}

	public void addSingelton(String name, List parameters) {
		if (Character.isUpperCase(name.charAt(0))) {
			name = name.substring(0, 1).toLowerCase() + name.substring(1);
		}

		VariableDefinition definition = new VariableDefinition("private", getClassName(), name, null);
		definition.setMakeStatic(true);
		addLocalVariable(definition);
		addMethod(MethodDefinition.getStandardSingleton(getClassName(), name, parameters));
	}

	public String getAnnotations() {
		return annotations;
	}

	public List<AccessorDefinition> getBeanMethods() {
		return beanMethods;
	}

	public abstract String getClassName();

	public abstract String getExtends();

	public List<String> getImplements() {
		return implementsDecl;
	}

	public Set<String> getImports() {
		return importsDecl;
	}

	public List<MethodDefinition> getMethodDefinitions() {
		return methods;
	}

	public abstract String getPackageName();

	public List<VariableDefinition> getVariableDefinitions() {
		return localVariables;
	}

	public String getVisability() {
		return visability;
	}

	public boolean isAbstract() {
		return isAbstract;
	}

	public boolean isClass() {
		return isClass;
	}

	/**
	 * Override this method it gets called once before toString do if toString
	 * gets called 5 time this will only be called on the first
	 */
	public void preWrite() {

	}

	public void setAbstract(boolean isAbstract) {
		this.isAbstract = isAbstract;
	}

	public void setAnnotations(String annotations) {
		this.annotations = annotations;
	}

	public void setClass(boolean isClass) {
		this.isClass = isClass;
	}

	public void setVisability(String visability) {
		this.visability = visability;
	}

	@Override
	public String toString() {
		if (!runPrewrite) {
			preWrite();
			runPrewrite = true;
		}

		StringBuffer sb = new StringBuffer();
		sb.append("/* Auto Generated by JavaGenerate http://code.google.com/p/javagenerate/ do not modify */\n");
		sb.append("package ").append(getPackageName()).append(";\n\n");
		if (getImports() != null) {
			List<String> list = new ArrayList<String>(getImports());
			Collections.sort(list);
			for (Iterator iter = list.iterator(); iter.hasNext();) {
				String element = (String) iter.next();
				sb.append("import ").append(element).append(";\n");
			}
		}
		sb.append("\n");
		if (annotations != null) {
			sb.append(annotations);
		}

		sb.append("\n");
		sb.append(getVisability());
		if (isClass()) {
			sb.append(" class ");
		} else {
			sb.append(" interface ");
		}
		sb.append(getClassName());
		if (typeStatement != null) {
			sb.append(" <").append(typeStatement).append("> ");
		}
		
		if (getExtends() != null && getExtends().length() > 0) {
			sb.append(" extends ").append(getExtends());
		}
		if (getImplements() != null && getImplements().size() > 0) {
			sb.append(" implements ");
			boolean first = true;
			for (Iterator iter = getImplements().iterator(); iter.hasNext();) {
				String element = (String) iter.next();
				if (!first) {
					sb.append(", ");
				}
				sb.append(element);
				first = false;
			}
		}
		sb.append(" {\n\n");
		if (localVariables != null) {
			Collections.sort(localVariables);
			for (Iterator iter = localVariables.iterator(); iter.hasNext();) {
				VariableDefinition element = (VariableDefinition) iter.next();
				sb.append(element).append("\n");
			}
		}

		if (constructors != null) {
			Collections.sort(constructors);
			for (Iterator iter = constructors.iterator(); iter.hasNext();) {
				ConstructorDefinition element = (ConstructorDefinition) iter.next();
				sb.append(element).append("\n\n");
			}
		}

		if (beanMethods.size() > 0 && isClass()) {
			MethodDefinition definition = new MethodDefinition("String", "toString");
			StringBuffer buffer = new StringBuffer();
			buffer.append("\tStringBuffer sb = new StringBuffer();");
			buffer.append("\n\tsb.append(super.toString());");
			for (Iterator iter = beanMethods.iterator(); iter.hasNext();) {
				AccessorDefinition element = (AccessorDefinition) iter.next();
				buffer.append("\n\tsb.append(\", ");
				buffer.append(element.getName()).append("=\").append(").append(element.getAccessorName()).append("());");
			}
			buffer.append("\n\treturn sb.toString();");
			definition.setContent(buffer.toString());
			methods.add(definition);
		}

		if (methods != null) {
			Collections.sort(methods);
			for (Iterator iter = methods.iterator(); iter.hasNext();) {
				MethodDefinition element = (MethodDefinition) iter.next();
				sb.append(element).append("\n\n");
			}
		}
		
		if (code != null) {
			sb.append(code);
		}

		sb.append("\n}");
		return sb.toString();
	}
}
